package it.sd.pwframework.business.service.impl;

import it.sd.pwframework.business.bean.PwForm;
import it.sd.pwframework.business.bean.PwProperty;
import it.sd.pwframework.business.bean.PwPropertyType;
import it.sd.pwframework.business.bean.PwRow;
import it.sd.pwframework.business.bean.PwRows;
import it.sd.pwframework.business.bean.PwValues;
import it.sd.pwframework.business.service.CommonService;
import it.sd.pwframework.persistence.annotations.CrudForm;
import it.sd.pwframework.persistence.annotations.CrudProperty;
import it.sd.pwframework.persistence.common.CommonSession;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.persistence.Column;

import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.CollectionType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("commonService")
public class CommonServiceImpl implements CommonService {

    protected Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass());
    
    private Hashtable<String, PwForm> forms = new Hashtable<String, PwForm>();
    
    @Autowired
    private CommonSession commonSession;
    
    @PostConstruct
    public void initForms() {
    	try {
    		
    		List<PwForm> lista = retrieveForms();
    		for (PwForm pwForm : lista) {
				forms.put(pwForm.getId(), pwForm);
			}
    		
    		log.info("forms initialized: " + forms);
    		
    	} catch(Exception e) {
    		log.error("Error getting list of forms", e);
    	}
    	
    }

    @Override
	public Collection<PwForm> getForms() throws Exception {
    	return forms.values();
    }

    @Override
	public PwForm getFormByClassname(String classname)  {
    	PwForm form = null;
    	
    	for (PwForm f: forms.values()) {
			if (f.getClassname().equals(classname )) {
				form = f;
				break;
			}
		}
    	
    	return form;
    }

    @Override
    @Transactional(propagation=Propagation.REQUIRED)
	public List<PwForm> retrieveForms() throws Exception {
    	
    	LinkedList<PwForm> lista = new LinkedList<PwForm>();
    	
    	Collection<ClassMetadata> coll = commonSession.listEntities();
    	for (ClassMetadata cmd: coll) {

    		PwForm bean = createForm(cmd);
    		lista.add(bean);
			

			
		}
    	
    	return lista;
    }
    
	private PwForm createForm(ClassMetadata cmd ) throws Exception {

		Class classe = cmd.getMappedClass();
		CrudForm formAnnotation = (CrudForm)classe.getAnnotation(CrudForm.class);

    	PwForm bean = new PwForm();
    	
    	bean.setClassname(classe.getCanonicalName());
    	bean.setId(classe.getCanonicalName().substring(classe.getCanonicalName().lastIndexOf(".")+1));
    	if (formAnnotation!=null) bean.setLabel(formAnnotation.label());
			
    	bean.setKeyname(cmd.getIdentifierPropertyName());
    	
    	String[] names 		= cmd.getPropertyNames();
    	boolean[] laziness	= cmd.getPropertyLaziness();
    	Type[] types 		= cmd.getPropertyTypes();
    	boolean[] nullable   = cmd.getPropertyNullability();
			
    	for (int i = 0; i < names.length; i++) {
    		PwProperty pb = new PwProperty(names[i]);
    		pb.setLazy(laziness[i]);
    		pb.setNullable(nullable[i]);

			Field dimWidthField = cmd.getMappedClass().getDeclaredField(names[i]);
			Column columnAnnotation = dimWidthField.getAnnotation(Column.class);

			CrudProperty crudProp= dimWidthField.getAnnotation(CrudProperty.class);
			if (crudProp!=null) {
				pb.setLabel(crudProp.label());
				pb.setOrder(crudProp.order());
				pb.setSearcheable(crudProp.searcheable());
				pb.setValidator(crudProp.validator());
				System.out.println(dimWidthField.getName() + ": " + crudProp.order());
			}
			
    		Type type = types[i];
    		if (type instanceof StringType ) {
    			pb.setType(PwPropertyType.STRING);
				if (columnAnnotation!=null) {
					if ("TEXT".equalsIgnoreCase(columnAnnotation.columnDefinition())) {
						pb.setType(PwPropertyType.TEXT);
					} else {
						pb.setLength(columnAnnotation.length());
					}
				} else {
					pb.setLength(255);
				}
    		} else if (type instanceof TimestampType) {
    			pb.setType(PwPropertyType.TIMESTAMP);
    		} else if (type instanceof CollectionType) {
    			CollectionType settype = (CollectionType) type;
    			pb.setType(PwPropertyType.SET);

    			pb.setSubtype(commonSession.getAssociatedEntityName(settype));
    			
    		} else {
    			log.error("Tipo non previsto: " + type.getClass());
    			continue;
    		}
    		
    		
    		
    		bean.getProperties().add(pb);
    	}
    	
    	bean.sort();
			
    	return bean;
		
	}

	@Override
	@Transactional(readOnly=true)
	public PwRows retrieveRows(PwForm bean) throws Exception {
		
		PwRows rows = new PwRows(bean);
		
		@SuppressWarnings("rawtypes")
		List lista = commonSession.findAll(bean.getClassname());
		for (Object object : lista) {
			rows.add(createValue(bean, object));
		}
		
		return rows;

	}

	
	@Override
	@Transactional(readOnly=true)
	public PwRow retrieveRow(PwForm bean, Long id) throws Exception {
		
		PwRow row = null;
		
		Object  object = commonSession.findById(bean.getClassname(), id);
		if (object!=null) {
			row = new PwRow(bean);
			row.setValues(createValue(bean, object));
		}
		
		return row;
	}

	@Override
	@Transactional(readOnly=false)
	public void delete(PwRow row) throws Exception {

		commonSession.delete(row.getForm().getClassname(), row.getValues().getId());

	}

	@Override
	@Transactional(readOnly=false)
	public void save(PwRow row) throws Exception {

		PwForm form = row.getForm();

		
		Object o = createObject(form, row.getValues()); 
				
		commonSession.save(o);

	}


	@Override
	public PwForm getForm(String id) throws Exception {
		return forms.get(id);
	}


	public PwValues createValue(PwForm form, Object o) {

		PwValues values = new PwValues();
		
		try {
			values.setId(Long.parseLong(BeanUtils.getProperty(o, form.getKeyname())));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		for (PwProperty p : form.getProperties()) {
			try {
				
				Object op = commonSession.getPropertyValue(form.getClassname(), o, p.getName());

				if (op!=null) {
					if (p.getType().equals(PwPropertyType.SET)) {
						// si tratta di una lista di elementi
						PwRows rows = new PwRows(getFormByClassname(p.getSubtype()));
						@SuppressWarnings("unchecked")
						Collection<Object> collection = (Collection<Object>) op;
						for (Object object : collection) {
							rows.add(createValue(getFormByClassname(p.getSubtype()), object));
						}
						values.put(p.getName(), rows);
					} else {
						
						values.put(p.getName(), op, p.getSearcheable());
					}
				}	
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
		
		return values;
		
		
	}


	private Object createObject(PwForm form, PwValues values) throws Exception {

		Object o = commonSession.newInstance(form.getClassname(), null);
		
		Object v = values.getId();
		if (v!=null) {
			BeanUtils.setProperty(o, form.getKeyname(), values.getId());
		}
		
		for (PwProperty p : form.getProperties()) {
			Object op = values.get(p.getName());
			if (op!=null) {
				if (p.getType().equals(PwPropertyType.SET)) {
					@SuppressWarnings("unchecked")
					Collection<PwValues> rows = (Collection<PwValues>) values.get(p.getName());
					@SuppressWarnings("unchecked")
					Collection<Object> collection = (Collection<Object>) commonSession.getPropertyValue(form.getClassname(), o, p.getName());
					for (PwValues row : rows) {
						collection.add(createObject(getFormByClassname(p.getSubtype()), row));
					}
					
				} else {
					BeanUtils.setProperty(o, p.getName(), op);
				}
			}
		}
		
		return o;
		
	}

	
}
